#! /usr/bin/python

'''
Created on 18/09/2010

@author: zainul
'''
from datetime import datetime
from types import NoneType
import CreateRuleUI
import LogViewerUI
import Miranda
import copy
import fcntl
import gobject
import gtk
import kronos
import os
import subprocess
import sys
import threading

CONFIRM_SAVE_PREF="Your Preferences have been Saved"
rule_dict = {}
config = Miranda.Config()
main_screen = None;
file_processing_scheduler = None;
scheduler_thread = None;
save_config_scheduler = None;
save_config_task = None;

class MainScreen:
    
    
    def __init__(self,glade_file='./Main Screen.glade'):
             
        """
        Setup the Folders Tab Widgets
        """       
        self.builder = gtk.Builder()
        self.builder.add_from_file(glade_file) 
       
        self.window = self.builder.get_object("window")
       
        self.builder.connect_signals(self)   
       
        #self.window.connect("delete-event", gtk.main_quit)
        self.window.connect("delete_event", self.__delete_event__)

        #pop up menu for the system icon tray
        menu = gtk.Menu()
        systray_quit_menu = gtk.ImageMenuItem(gtk.STOCK_QUIT)
        systray_manage_menu = gtk.MenuItem('Manage');
        systray_recycle_menu = gtk.MenuItem('Recycle Bin');
        systray_pref_menu = gtk.MenuItem('Preference');
       
        #create a sys tray icon
        self.systray_icon = gtk.status_icon_new_from_stock(gtk.STOCK_GOTO_TOP)
        self.systray_icon.connect('activate', self.status_clicked )
        self.systray_icon.connect('popup-menu', self.popup_menu_cb, menu)
        self.systray_icon.set_tooltip("the window is visible")
               
        systray_quit_menu.connect('activate', self.__quit_application__, self.systray_icon)
        systray_manage_menu.connect('activate',lambda fn: self.__set_selected_tab__(0))
        systray_recycle_menu.connect('activate',lambda fn: self.__set_selected_tab__(1))
        systray_pref_menu.connect('activate',lambda fn: self.__set_selected_tab__(2))
       
        
        menu.append(systray_quit_menu)
        menu.append(systray_manage_menu)
        menu.append(systray_recycle_menu)
        menu.append(systray_pref_menu)

        self.builder.get_object("add_folder_btn").connect("clicked",lambda fn: self.__add_folder__())
        self.builder.get_object("remove_folder_btn").connect("clicked",lambda fn: self.__remove_folder__())
        self.builder.get_object("add_rule_btn").connect("clicked",lambda fn: self.__add_rule__())
        self.builder.get_object("edit_rule_btn").connect("clicked",lambda fn: self.__edit_rule__())
        self.builder.get_object("remove_rule_btn").connect("clicked", lambda fn: self.__remove_rule__())
        self.builder.get_object("disable_btn").connect("clicked", lambda fn: self.__disable_rule__())
        self.notebook = self.builder.get_object("notebook")
       
        #Folder List, initialize signals,column and list store
        self.folder_list = self.builder.get_object("folder_list")       
        self.folder_list.get_selection().connect("changed",lambda fn: self.__select_folder__())
       

        self.folder_list_store = gtk.ListStore(gobject.TYPE_STRING)       
         
        self.folder_list.set_model(self.folder_list_store);
       
        self.cell = gtk.CellRendererText()
        self.folder_column = gtk.TreeViewColumn('Folder') 
        self.folder_column.pack_start(self.cell,expand=True);
        self.folder_column.add_attribute(self.cell,'text',0)
        self.folder_column.set_cell_data_func(self.cell,self.__folder_name__)      
        self.folder_list.append_column(self.folder_column)         
        self.folder_list.set_model(self.folder_list_store)
       
     
         
        global config
        global rule_dict
        
        config  = Miranda.read_config()
        rule_dict = config.rule_dict
              
        self.__populate_folder_list__(rule_dict.keys())
           
        #Rule List, initialize signals,column and list store
        self.rule_list = self.builder.get_object("rule_list")             
        self.rule_list.connect("cursor-changed",lambda fn: self.__rule_cell_clicked__())     
        self.rule_list_store = gtk.ListStore(gobject.TYPE_STRING,gobject.TYPE_STRING)                          
        self.rule_list.set_model(self.rule_list_store);
       
        self.enable_cell = gtk.CellRendererText()
        self.enable_column = gtk.TreeViewColumn('Enable ?') 
        self.enable_column.pack_start(self.enable_cell,expand=True);
        self.enable_column.add_attribute(self.enable_cell,'text',0)
       
        self.rule_cell = gtk.CellRendererText()
       
       
        self.rule_column = gtk.TreeViewColumn('Rule') 
        self.rule_column.pack_start(self.rule_cell,expand=True);
        self.rule_column.add_attribute(self.rule_cell,'text',1)
              
        self.rule_list.append_column(self.enable_column)
        self.rule_list.append_column(self.rule_column)
         
        self.rule_list.set_model(self.rule_list_store)
       
        self.observer = RulesObserver(self.rule_list_store)
        self.replace_rules_observer = ReplaceRuleObserver(self.rule_list_store)
       
        Miranda.observer = self.observer;
       
        '''
        Setup the Recycle Bin folders tab
        '''       
        self.builder.get_object("empty_recycle_bin_chkbox").set_sensitive(False)
        self.builder.get_object("empty_interval_txt").set_sensitive(False)
        self.builder.get_object("empty_bin_uom_combo").set_sensitive(False)
        self.builder.get_object("enable_rubbish_bin_mgr").connect("clicked",lambda fn: self.__enable_bin_widgets__(self.builder.get_object("enable_rubbish_bin_mgr").get_active()))
        self.builder.get_object("empty_recycle_bin_chkbox").connect("clicked",lambda fn: self.__enable_bin_widgets__(self.builder.get_object("empty_recycle_bin_chkbox").get_active()))
        self.builder.get_object("save_bin_pref_btn").connect("clicked",lambda fn: self.__save_rubbish_bin_pref__())
       
        self.uom_list = gtk.ListStore(gobject.TYPE_STRING)
        self.empty_bin_uom_combo = self.builder.get_object("empty_bin_uom_combo") 
        self.empty_bin_uom_combo.set_model(self.uom_list);
       
        cell = gtk.CellRendererText()
        self.empty_bin_uom_combo.pack_start(cell, True)
        self.empty_bin_uom_combo.add_attribute(cell, 'text', 0)
       
        self.empty_bin_uom_combo.append_text(Miranda.MINUTES)
        self.empty_bin_uom_combo.append_text(Miranda.HOURS)
        self.empty_bin_uom_combo.append_text(Miranda.DAYS)
        self.empty_bin_uom_combo.append_text(Miranda.WEEKS)       
       
        #setup the recycle bin tab based on the config file
        self.__populate_recycle_bin_tab__()
       
       
        self.builder.get_object("sleeptime_btn").connect("clicked", lambda fn: self.__save_sleep_time__())
       
        '''Setup the File Menu'''
        self.pause_menu = self.builder.get_object("pause_menu")
        self.pause_menu.connect("activate",lambda fn: self.__pause__())
        self.builder.get_object("backup_menu").connect("activate",lambda fn: self.__back_up_settings__())
        self.builder.get_object("import_menu").connect("activate",lambda fn: self.__import_file__())
        self.builder.get_object("view_log_menu").connect("activate",lambda fn: self.__view_log__())
        self.builder.get_object("exit_menu").connect("activate",lambda fn: self.__quit_application__(widget=None,data= self.systray_icon))
       
        '''Setup Help Menu'''
        self.builder.get_object("help_submenu").connect("activate", lambda fn: self.__help_menu__());
        self.builder.get_object("about_submenu").connect("activate", lambda fn: self.__about_menu__());
       
        self.file_processing_paused = False
    
    def __help_menu__(self):
        subprocess.call(('gnome-open', './Cruftbuster Help.pdf'))
        
    def __about_menu__(self):
        self.about_menu_builder = gtk.Builder()
        self.about_menu_builder.add_from_file("./about.glade")
       
        self.about_menu_builder.get_object("label1").set_line_wrap(True)
        self.about_menu_builder.get_object("label2").set_line_wrap(True)
        self.about_menu_window = self.about_menu_builder.get_object("about_window")       
        self.about_menu_builder.connect_signals(self) 
        self.about_menu_window.show()
        
    def __folder_name__(self, column, cell, model, iter):
        cell.set_property('text',os.path.basename(model.get_value(iter,0)))
        return

    def __set_selected_tab__(self,tab_num):        
        self.notebook.set_current_page(tab_num)
        self.window.show_all()
        
    def popup_menu_cb(self,widget, button, time, data = None):
        if button == 3:
            if data:
                data.show_all()
                data.popup(None, None, None, 3, time)
            
    def __quit_application__(self,widget= None, data = None):
                    
        def shutdown_app():
            
            if data:
                data.set_visible(False)
            
            self.window.destroy();
            
            #stop file processing and config save
            file_processing_scheduler.cancel_file_processing_schedule()
            global save_config_scheduler,config,save_config_task        
            save_config_scheduler.cancel(save_config_task)
        
            #save config
            Miranda.dump_rules(config);
        
            gtk.main_quit()
        
        Miranda.open_confirm_action_dialog(shutdown_app, "Are you sure you want to quit Cruftbuster?", self.window)    
            
    def status_clicked(self,status):
        #unhide the window
        self.window.show_all()
        self.systray_icon.set_tooltip("the window is visible")
       
    def __delete_event__(self,window=None,event=None):
        #don't delete; hide instead
        self.window.hide_on_delete()
        self.systray_icon.set_tooltip("the window is hidden")
        return True
    
    def __view_log__(self):
        log_viewer_ui = LogViewerUI.LogViewerUI()
        log_viewer_ui.window.show()
        
    def __populate_sleep_time__(self):
        self.builder.get_object("sleeptime_txt").set_text(str(config.sleeptime));
    
    def __import_file__(self):
        
        def open_import_dialog():
            dialog = gtk.FileChooserDialog("Open..",
                                   None,
                                   gtk.FILE_CHOOSER_ACTION_OPEN,
                                   (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                    gtk.STOCK_OPEN, gtk.RESPONSE_OK))
            dialog.set_default_response(gtk.RESPONSE_OK)
                      
            response = dialog.run()
           
            if response == gtk.RESPONSE_OK:
                import_file = dialog.get_filename()
                
                global config
                global rule_dict
                
                config = Miranda.read_config(import_file);
                rule_dict = config.rule_dict
                
            dialog.destroy()
            self.__populate_recycle_bin_tab__();
            self.__populate_folder_list__(rule_dict.keys());        
            self.__populate_recycle_tab__(config);
            self.__populate_sleep_time__()
            
        def on_dialog_key_press(dialog, event):
                
            if (event == gtk.RESPONSE_CANCEL):
                return;
                            
            if (event == gtk.RESPONSE_OK):
                open_import_dialog()
        
        md = gtk.MessageDialog(self.window, gtk.DIALOG_MODAL, 
                           gtk.MESSAGE_WARNING, gtk.BUTTONS_OK_CANCEL, 'Import will Overwrite Current Settings');    
        md.connect('response', on_dialog_key_press)    
        md.run()
        md.destroy()
        
        
    def __populate_recycle_bin_tab__(self):
        self.builder.get_object("enable_rubbish_bin_mgr").set_sensitive(True)
        self.builder.get_object("enable_rubbish_bin_mgr").set_active(config.bin_manager.is_bin_mgmt_enabled)
       
        self.builder.get_object("empty_recycle_bin_chkbox").set_sensitive(config.bin_manager.is_empty_recycle_bin_enabled);       
        self.builder.get_object("empty_recycle_bin_chkbox").set_active(config.bin_manager.is_empty_recycle_bin_enabled);
              
        self.builder.get_object("empty_bin_uom_combo").set_sensitive(config.bin_manager.is_empty_recycle_bin_enabled)
       
        if not isinstance(config.bin_manager.uom, NoneType):
            for i in range (len(self.uom_list)):
                if(self.uom_list[i][0] == config.bin_manager.uom):
                    self.empty_bin_uom_combo.set_active(i);
                    break;
       
        self.builder.get_object("empty_interval_txt").set_sensitive(config.bin_manager.is_bin_mgmt_enabled)
        self.builder.get_object("empty_interval_txt").set_text(str(config.bin_manager.interval))              
       
        
    def __populate_folder_list__(self,folder_list):
             
        for each_folder in folder_list:
            self.folder_list_store.append([each_folder]);
    
    def __populate_recycle_tab__(self,config):
        pass;
        
    def __pause__(self):
        
        pause_menu = self.builder.get_object("file_submenu").get_children()[0]
        self.file_processing_paused = not self.file_processing_paused
        
        if(self.file_processing_paused):
            pause_menu.get_child().set_text('Resume')
            file_processing_scheduler.cancel_file_processing_schedule()
        else:
            pause_menu.get_child().set_text('Pause')
            self.__restart_file_processing_thread__()
       
    def __back_up_settings__(self):
         
        save_dialog = gtk.FileChooserDialog("Open..",
                                   None,
                                   gtk.FILE_CHOOSER_ACTION_SAVE,
                                   (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                    gtk.STOCK_SAVE,gtk.RESPONSE_OK))
        
        response = save_dialog.run()
        
        if (response == gtk.RESPONSE_OK):
            back_up_file = save_dialog.get_filename()
            Miranda.dump_rules(config,back_up_file);
            
        save_dialog.destroy()
    
    def __restart_file_processing_thread__(self):        
                
        gtk.gdk.threads_init()                        
        
        gtk.gdk.threads_enter()
        
        start_scheduler_thread();
        
        gtk.gdk.threads_leave()
        
    def __save_sleep_time__(self):
        
        try:               
            config.sleeptime = int(self.builder.get_object("sleeptime_txt").get_text());     
        except ValueError:
            Miranda.open_msg_dialog(self.window, 'Please enter a number')
            return
        
        scheduler_thread.stop()
        file_processing_scheduler.cancel_file_processing_schedule()           
        self.__restart_file_processing_thread__()
        Miranda.open_msg_dialog(self.window, CONFIRM_SAVE_PREF)
        
    def __save_rubbish_bin_pref__(self):
                
        uom = self.builder.get_object("empty_bin_uom_combo").get_active_text()
        interval = int(self.builder.get_object("empty_interval_txt").get_text())
        
        bin_manager  = Miranda.BinManager();
        bin_manager.is_empty_recycle_bin_enabled = self.builder.get_object("empty_recycle_bin_chkbox").get_active();
        bin_manager.is_bin_mgmt_enabled = self.builder.get_object("enable_rubbish_bin_mgr").get_active();
        bin_manager.interval = interval;
        bin_manager.uom = uom
        
        if(isinstance(config.bin_manager.first_enabled_time,NoneType)):            
            bin_manager.first_enabled_time = datetime.now()
        else:
            bin_manager.first_enabled_time = config.bin_manager.first_enabled_time
            
        config.bin_manager = bin_manager    
        Miranda.open_msg_dialog(self.window, CONFIRM_SAVE_PREF)    
    
    def __get_selected_rule__(self):
        selected_rule = self.rule_list.get_selection().get_selected()
        rule_iter = selected_rule[1]
        
        if not isinstance(rule_iter, NoneType):
            rule_setting = Miranda.RuleSetting();
            
            rule_name = self.rule_list_store.get_value(rule_iter,1)   
        
            folder_iter = self.__find_selected_folder__()              
            folder_name = self.folder_list_store.get_value(folder_iter,0) 
        
            rule_setting.description = rule_name;
            rule_setting.source_folder = folder_name;
        
            rule = Miranda.Rule();
            rule.rule_setting = rule_setting;
        
            rules = rule_dict.get(folder_name)                
        
            try:
                index = rules.index(rule)
                return rules[index]
            except ValueError:
                return;
            
    def __disable_rule__(self):
        
        rule = self.__get_selected_rule__()    
        disabled_rule = copy.deepcopy(rule)
        disabled_rule.rule_setting.enabled =  not rule.rule_setting.enabled;
        disabled_rule.isNew = False
        self.replace_rules_observer.save_config(disabled_rule);    
        
    def __enable_bin_widgets__(self, enabled):
        
        self.builder.get_object("empty_recycle_bin_chkbox").set_sensitive(enabled)    
        self.builder.get_object("empty_recycle_bin_chkbox").set_active(enabled)
        self.builder.get_object("empty_interval_txt").set_sensitive(enabled)
        self.builder.get_object("empty_bin_uom_combo").set_sensitive(enabled)
        
    def __find_selected_folder__(self):
        
        selected_row = self.folder_list.get_selection().get_selected()
        tree_iter = selected_row[1]
                        
        return tree_iter
    
    def __select_folder__(self):
                
        folder_list_iter = self.__find_selected_folder__()
        
        if isinstance(folder_list_iter, NoneType):
            return
                              
        folder_name = self.folder_list_store.get_value(folder_list_iter,0)
        
        ''' display the folder directory name'''
        self.builder.get_object("folder_path_lbl").set_text(os.path.dirname(folder_name))
        
        ''' display the rules associated with the folder '''
        rule_list = rule_dict.get(folder_name);
        
        self.rule_list_store.clear();
        
        if not isinstance(rule_list, NoneType):
            
            for each_rule in rule_list:
                
                if each_rule.rule_setting.description == '':
                    continue;
                
                self.rule_list_store.append([each_rule.rule_setting.enabled,each_rule.rule_setting.description]);
                    

    def __remove_folder__(self):
        folder_list_iter = self.__find_selected_folder__()
        
        if isinstance(folder_list_iter, NoneType):
            Miranda.open_msg_dialog(self.window, "Select a Folder");
            return;
        
        folder_name = self.folder_list_store.get_value(folder_list_iter,0)       
        
        self.folder_list_store.remove(folder_list_iter)
        self.rule_list_store.clear();
        rule_dict.pop(folder_name);
        config.rule_dict = rule_dict;
    
    def __rule_cell_clicked__(self):
        rule = self.__get_selected_rule__()
        disable_btn = self.builder.get_object("disable_btn");
        
        if rule.rule_setting.enabled:
            disable_btn.set_label("Disable")
        else:
            disable_btn.set_label("Enable")   
        
    def __remove_rule__(self):
        
        folder_iter = self.__find_selected_folder__()
               
        if isinstance(folder_iter, NoneType):
            Miranda.open_msg_dialog(self.window, "Select a Rule");
            return;
               
        folder_name = self.folder_list_store.get_value(folder_iter,0)
        
        selected_rule = self.rule_list.get_selection().get_selected()
        rule_iter = selected_rule[1]
        rule_name = self.rule_list_store.get_value(rule_iter,1)   
        self.observer.remove_rule(rule_name, folder_name);

    def __edit_rule__(self):
        
        rule = self.__get_selected_rule__()   
        
        if isinstance(rule, NoneType):
            Miranda.open_msg_dialog(self.window, "Select a Rule");
            return;
        
        createRuleEditor = CreateRuleUI.CreateRuleEditor(rule)  
        createRuleEditor.main_screen_observer = self.replace_rules_observer
        createRuleEditor.set_edited_rule(rule)      
        createRuleEditor.window.show()

    def __add_rule__(self):
                
        folder_list_iter = self.__find_selected_folder__()
        
        if isinstance(folder_list_iter, NoneType):
            Miranda.open_msg_dialog(self.window, "Select a Folder");
            return;
        
        source_folder = self.folder_list_store.get_value(folder_list_iter,0) 
        
        create_rule_editor = CreateRuleUI.CreateRuleEditor()
        create_rule_editor.main_screen_observer = self.observer
        create_rule_editor.create_new_rule(source_folder)        
        create_rule_editor.window.show()

 
    def __add_folder__(self):
        dialog = gtk.FileChooserDialog("Open..",
                                   None,
                                   gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                                   (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                    gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)
                      
        response = dialog.run()
       
        if response == gtk.RESPONSE_OK:
            new_folder = dialog.get_filename()
            self.folder_list_store.append([new_folder]);
            rule_setting = Miranda.RuleSetting();
            rule_setting.source_folder = new_folder             
            rule = Miranda.Rule()
            rule.rule_setting = rule_setting
            config.rule_dict[new_folder] = []
            
        dialog.destroy()

class RulesObserver:

    def __init__(self,rule_list_store):        
        self.rule_list_store = rule_list_store

    def remove_rule(self,rule_name,folder_name):
        rule_setting = Miranda.RuleSetting();
        rule_setting.description = rule_name;
        rule_setting.source_folder = folder_name;
    
        rule = Miranda.Rule();
        rule.rule_setting = rule_setting
    
        rule_list = rule_dict[folder_name];
        rule_list.remove(rule);
        rule_dict[folder_name] = rule_list
        config.rule_dict = rule_dict;
        
        self.update_rule_list(folder_name);
        
    def save_config(self,rule):
    
        self.rule_list_store.append([rule.rule_setting.enabled,rule.rule_setting.description]);
        folder_name = rule.rule_setting.source_folder
        keys = rule_dict.keys()
            
        try:
            keys.index(folder_name)
        except ValueError:
            rules = [rule]
            rule_dict[folder_name] = rules
            return
        
        rule_list = rule_dict[folder_name];
        
        try:
            rule_list.remove(rule)
        except ValueError:
            pass;
        
        rule_list.append(rule);
        rule_dict[folder_name] = rule_list
        config.rule_dict = rule_dict
        
                   
    def update_rule_list(self,folder_name):
        
        self.rule_list_store.clear();
        rules = rule_dict.get(folder_name)
        
        for each_rule in rules:
               
            if(each_rule.rule_setting.description == ''):
                continue;         
            
            self.rule_list_store.append([each_rule.rule_setting.enabled,each_rule.rule_setting.description]);

stCount = 0
class StoppableThread (threading.Thread):
    
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""
    
    def __init__ (self,thread_target):
        super(StoppableThread, self).__init__(target=thread_target)        
        self._stop = threading.Event()
    
    def stop (self):
        self._stop.set()

    def stopped (self):
        return self._stop.isSet()
    
def start_scheduler_thread():
    
                
    def __run_scheduler__():
            
        global file_processing_scheduler;
        file_processing_scheduler = Miranda.Scheduler();
        file_processing_scheduler.config = config;
        file_processing_scheduler.schedule()
    
    
    global scheduler_thread,id   
    scheduler_thread = StoppableThread(thread_target= __run_scheduler__)
    scheduler_thread.start()

class ReplaceRuleObserver(RulesObserver):
    
    def save_config (self, rule, folder_name = None):
        rule_list = rule_dict[rule.rule_setting.source_folder];
        index = rule_list.index(rule)
        rule_list.remove(rule);
        rule_list.insert(index,rule);
        rule_dict[rule.rule_setting.source_folder] = rule_list;
        config.rule_dict = rule_dict;
        self.update_rule_list(rule.rule_setting.source_folder);


def __save_config_scheduler__():        
    
    def __save_config__():
        global config
        Miranda.dump_rules(config);
    
    global save_config_scheduler,save_config_task 
    
    save_config_scheduler = kronos.Scheduler();            
    save_config_task = save_config_scheduler.add_interval_task(__save_config__, 'Save Config Scheduler', 5, 5, kronos.method.sequential, [], {});                    
    save_config_scheduler.start();
    
def show_error_dlg(error_string):
    """This Function is used to show an error dialog when
    an error occurs.
    error_string - The error string that will be displayed
    on the dialog.
    """
    error_dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR
                                  , message_format=error_string
                                  , buttons=gtk.BUTTONS_OK)
    error_dlg.run()
    error_dlg.destroy()
                    
if __name__ == "__main__":
    
    Miranda.create_config_file()
    Miranda.init_logging()
    gtk.gdk.threads_init()                        
        
    gtk.gdk.threads_enter()
    
    try:
        fd = open("/tmp/cruftbuster-lock", 'w') 
        fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        main_screen = MainScreen()
        
        
        if len(sys.argv) > 1 and sys.argv[1] == '-minimized':
            main_screen.__delete_event__()
        else:
            main_screen.window.show()
        
    except IOError:
        show_error_dlg("Only one instance of Cruftbuster is allowed at a time.")
        raise SystemExit
    
    Miranda.main_screen = main_screen
    start_scheduler_thread();
    
    save_config_thread = StoppableThread(thread_target=__save_config_scheduler__)
    save_config_thread.start()
    
    
    gtk.main()
    gtk.gdk.threads_leave()
    
    # stop running kronos sched threads
    save_config_scheduler.stop()
    file_processing_scheduler.stop()